#include <linux/init.h>
#include <linux/module.h>
#include <linux/mempool.h>
#include <linux/slab.h>

#include "vd.h"
#include "vd-io.h"
#include "device-mapper.h"

#define VD_MSG_PREFIX "core"

#define VDF_BLOCK_IO 0
#define VDF_SUSPENDED 1
#define VDF_FROZEN 2
#define VDF_FREEING 3
#define VDF_DELETING 4
#define VDF_OPENED 5

struct mapped_device {
    struct rw_semaphore io_lock;
    struct semaphore suspend_lock;
    rwlock_t map_lock;
    atomic_t holders;

    unsigned long flags;

    char name[16];

    void *interface_ptr;

    /*
     * A list of ios that arrived while we were suspended.
     */
    atomic_t pending;
    wait_queue_head_t wait;

    struct list_head cmndlist;

    /*
     * The current mapping.
     */
    struct vd_table *map;

    /*
     * io objects are allocated from here.
     */
    mempool_t *io_pool;

    /*
     * Event handling.
     */
    atomic_t event_nr;
    wait_queue_head_t eventq;
};

#define MIN_IOS 256
static struct  kmem_cache *_io_cache;

#define MAX_VOLUMES 1024 
static struct mapped_device **md_array;
static unsigned int device_id;

static DEFINE_SPINLOCK(vd_lock);

static inline struct vd_io *alloc_io(struct mapped_device *md)
{
    return mempool_alloc(md->io_pool, GFP_KERNEL);
}

static inline void free_io(struct mapped_device *md, struct vd_io *vio)
{
    mempool_free(vio, md->io_pool);

    return;
}
unsigned int  done(struct Target_Scsi_Cmnd_t* cmnd)
{
    struct vd_io *vio = (struct vd_io *)cmnd->private;

    cmnd->private = vio->org_private;
    cmnd->done = vio->done;

    free_io(vio->md, vio);

    return cmnd->done(cmnd);
}

int distribute_io(struct vd_io *vio, Target_Scsi_Cmnd *cmnd)
{
    struct vd_target *ti;

    vio->map = vd_get_table(vio->md);
    if(!vio->map){
        BUG_ON(!vio->map);
        return -1;
    }

    ti = vd_table_find_target(vio->map, cmnd->length >> 9);
    ti->type->map(ti, cmnd, NULL);
    
    return ti->type->make_request(ti, cmnd, vio->rw);   
}

int  make_request(struct Target_Scsi_Cmnd_t *cmnd, int rw)
{
    struct vd_io *vio;
    struct mapped_device *md = (struct mapped_device *)cmnd->device;

    vio = alloc_io(md);
    if(!vio)
    {
        VDERR("No Mem");
        BUG_ON(!vio);
       
        return  -ENOMEM;
    }

    vio->rw = rw;
    vio->md = md;
    vio->done = cmnd->done;
    vio->org_cmnd = cmnd;
    vio->org_private = cmnd->private;
    cmnd->private = vio;

    if(test_bit(VDF_BLOCK_IO, &md->flags))
    {

    }

    distribute_io(vio, cmnd);

    return 0;
}

static int __init local_init(void){
    _io_cache = KMEM_CACHE(vd_io, 0);
    if (!_io_cache)
        return -ENOMEM;

    md_array = kmalloc(MAX_VOLUMES * sizeof(struct mapped_device *), GFP_KERNEL);
    if(!md_array)
        return -ENOMEM;
    
    memset(md_array, 0, MAX_VOLUMES * sizeof(struct mapped_device *));

    device_id = 0;

    return 0;

}

static void local_exit(void)
{
    kmem_cache_destroy(_io_cache);
    kfree(md_array);

    return;
}

/*
 * Allocate and initialise a blank device with a given minor.
 */
static struct mapped_device *alloc_md(void)
{
    struct mapped_device *md = kmalloc(sizeof(*md), GFP_KERNEL);

    if (!md) {
        VDWARN("unable to allocate device, out of memory.");
        return NULL;
    }

    if (!try_module_get(THIS_MODULE))
        goto bad0;


    memset(md, 0, sizeof(*md));
    init_rwsem(&md->io_lock);
    init_MUTEX(&md->suspend_lock);
    rwlock_init(&md->map_lock);
    atomic_set(&md->holders, 1);
    atomic_set(&md->event_nr, 0);

    md->io_pool = mempool_create_slab_pool(MIN_IOS, _io_cache);
     if (!md->io_pool)
         goto bad1;

    atomic_set(&md->pending, 0);
    init_waitqueue_head(&md->wait);
    init_waitqueue_head(&md->eventq);

    return md;

 bad1:
    module_put(THIS_MODULE);
 bad0:
    kfree(md);
    return NULL;
}

static void free_dev(struct mapped_device *md)
{
    mempool_destroy(md->io_pool);
    module_put(THIS_MODULE);
    kfree(md);

    return;
}

int create_md(struct mapped_device **result)
{
    struct mapped_device *md;

    md = alloc_md();
    if (!md)
        return -ENXIO;

    *result = md;
    
    return 0;
}

struct mapped_device *vd_get_md(uint32_t id)
{
    return md_array[id];
}

void *vd_get_mdptr(struct mapped_device *md)
{
    return md->interface_ptr;
}

void vd_set_mdptr(struct mapped_device *md, void *ptr)
{
    md->interface_ptr = ptr;
}

void vd_get(struct mapped_device *md)
{
    atomic_inc(&md->holders);
}

const char *vd_device_name(struct mapped_device *md)
{
    return md->name;
}
EXPORT_SYMBOL(vd_device_name);

/*
 * Everyone (including functions in this file), should use this
 * function to access the md->map field, and make sure they call
 * dm_table_put() when finished.
 */
struct vd_table *vd_get_table(struct mapped_device *md)
{
    struct vd_table *t;

    read_lock(&md->map_lock);
    t = md->map;
    if (t)
        vd_table_get(t);
    read_unlock(&md->map_lock);

    return t;
}

void vd_put(struct mapped_device *md)
{
    return;
}

int vd_lock_for_deletion(struct mapped_device *md)
{
    int r = 0;

    spin_lock(&vd_lock);

    if (test_bit(VDF_OPENED, &md->flags))
        r = -EBUSY;
    else
        set_bit(VDF_DELETING, &md->flags);

    spin_unlock(&vd_lock);

    return r;
}


int (*_inits[])(void) __initdata = {
    local_init,
    vd_interface_init,
    vd_linear_init,
};

void (*_exits[])(void) = {
    local_exit,
    vd_interface_exit,
    vd_linear_exit,
};

static int __init vd_init(void)
{
    const int count = ARRAY_SIZE(_inits);
     int r, i;
    
    VDINFO("Virtual Disk Module Loaded");

    for (i = 0; i < count; i++) {
        r = _inits[i]();
        if (r)
            goto bad;
    }

    return 0;

    bad:
    while (i--)
        _exits[i]();

    return r;
}

static void __exit vd_exit(void)
{
    int i = ARRAY_SIZE(_exits);

    VDINFO("Virtual Disk Module Unloaded");

    while (i--)
        _exits[i]();
    
    return;
}

module_init(vd_init);
module_exit(vd_exit);

MODULE_LICENSE("GPL");

